using System;
using System.Collections;
using System.IO;
using System.Xml.Serialization;
using System.Collections.Generic;
using System.Linq;
using SQLRunnerLib.Exceptions;
using SQLRunnerLib.Services;

namespace SQLRunnerLib.Project
{
	/// <summary>
	/// Container for the collection of scripts that need to be executed
	/// in a SQL Server
	/// A project class consists of a list of few default properties and a
	/// collection of folders. A folder may contains other folders and a list
	/// of scripts.
	/// </summary>
	[Serializable, XmlRoot(Namespace="", IsNullable=false)]
	public class SQLProject
	{
		private string _filePath;

	    [XmlArrayItem("Folder", typeof(Folder))]
		public List<Folder> Folders = new List<Folder>();

		[XmlArrayItem("Extension", typeof(string))]
        public List<string> Extensions = new List<string>();

        [XmlArrayItem("Environment", typeof(SQLEnvironment))]
        public List<SQLEnvironment> Environments = new List<SQLEnvironment>();

		#region Public Helpers

		/// <summary>
		/// Serializes the SQLProject instance and saves it in the passed
		/// full path file.
		/// </summary>
		/// <param name="fileName">Full path of the file to save the serialized instance</param>
		public void Serialize(string filePath)
		{
			XmlSerializer aSerializer = new XmlSerializer(typeof(SQLProject));
			using(StreamWriter aWriter = new StreamWriter(filePath))
			{
				aSerializer.Serialize(aWriter, this);
				aWriter.Close();
				_filePath = filePath;
			}
		}
		
		/// <summary>
		/// The full path of the file that was used to serialized/deserialized
		/// the current instance
		/// </summary>
		/// <returns>Full path of the file</returns>
		public string FileFullPath() 
        {
			return ( _filePath ?? string.Empty );
		}

        public SQLEnvironment SelectedEnvironment
        {
            get
            {
                return Environments.Where(e => e.IsSelected).FirstOrDefault();
            }
        }


		#endregion

		#region Static Helpers

		/// <summary>
		/// Reads a XML file that can be deserialized into a SQLProject instance
		/// </summary>
		/// <param name="fileName">the full file path of the file that
		/// contains the serialised SQLProject instance</param>
		/// <returns></returns>
		public static SQLProject Deserialize(string fileName)
		{
			SQLProject sqlProject;
			XmlSerializer aSerializer = new XmlSerializer(typeof(SQLProject));
            // WI#10777 - SQLRunnerCommand unable to read .sqlrunner file defect
            // Project file cannot be open if file is READ ONLY => Set access to ONLY Read
			using (FileStream aReader = new FileStream(fileName, FileMode.Open, FileAccess.Read))
			{
				sqlProject = (SQLProject) aSerializer.Deserialize(aReader);
				aReader.Close();
				sqlProject._filePath = fileName;
			}
			return sqlProject;
		}

		/// <summary>
		/// Retrieves the list of files in the root of the passed folder. If
		/// the IncludeAllFiles is TRUE then it returns all the files that match
		/// to the project extensions
		/// </summary>
		/// <param name="folder">Folder full path</param>
		/// <param name="project">Project that contains the passed folder</param>
		/// <returns>List of files to run that where found at the root of the passed folder</returns>
		public List<FileInfo> FilesInRootFolder(Folder folder) 
        {
			DirectoryInfo mainDir = new DirectoryInfo(folder.Path);
			// run files before subfolders
            List<FileInfo> files = new List<FileInfo>();
			if (folder.IncludeAllFiles) 
			{
				foreach (string ext in this.Extensions) {
					files.AddRange(mainDir.GetFiles("*." + ext));
				}
			}
			else 
			{
				foreach (string file in folder.Files) {
					FileInfo fileinfo = new FileInfo(Path.Combine(folder.Path, file));
					files.Add(fileinfo);
				}
			}
			return files;
		}

		/// <summary>
		/// All the files contained in a particular folder
		/// </summary>
		/// <param name="folder">Folder full path</param>
		/// <param name="project">Project that contains the passed folder</param>
		/// <returns>Full path of all the files in the passed folder</returns>
        private List<FileInfo> FilesInFolder(Folder folder)
        {
            List<FileInfo> files = this.FilesInRootFolder(folder);
			if (folder.IncludeAllSubFolders) {
				foreach (Folder subfolder in folder.Folders) {
					files.AddRange(FilesInRootFolder(subfolder));
				}
			}
			return files;
		}

		/// <summary>
		/// Obtains the full list of files that a SQLProject contains
		/// </summary>
		/// <returns>Full path of all the files in the SQLProject</returns>
		public List<FileInfo> FilesInProject() 
        {
            List<FileInfo> files = new List<FileInfo>();
			foreach (Folder folder in this.Folders) {
				files.AddRange(FilesInFolder(folder));
			}
			return files;
		}

        //TODO: How much should this validate?  We could iterate through each environment and call Validate on it.
        public void Validate()
        {
            if (this.Folders.Count == 0)
                throw new ValidationException("Project file should contain at least one folder");

            // Just give a warning if there are no environments.  An environment is not necessary when connection line args are supplied.
            // Allows users to use their existing project files.
            if (!(this.Environments.Count > 0))
                ServiceContainer.Instance.Logger.LogMessage("This project file does not contain an environment section, was this project created in an old version?");
        }

		#endregion

	}
}
